﻿/*********************************************************

    Copyright (c) Microsoft. All rights reserved.
    This code is licensed under the Microsoft Public License.
    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.

*********************************************************/
using System.Data.Services.Client;
using System;
using System.Linq;
using System.Net;
using System.Net.Browser;
using System.Xml.Linq;

namespace OData.Silverlight
{
    public class ODataServiceContext : DataServiceContext
    {
        private const string ODataMetadataNamespace = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";
        private const string ODataDataNamespace = "http://schemas.microsoft.com/ado/2007/08/dataservices";
        private const string ODataTypeName = "type";
        private const string ODataHref = "href";
        private const string ODatarel = "rel";
        private const string ODataSource = "src";
        private const string ODataEditLinkType = "edit";
        private const string ODataTitle = "title";
        private const string ODataCollectionNavigationPropertyLinkType = "application/atom+xml;type=feed";
        private const string ODataReferenceNavigationPropertyLinkType = "application/atom+xml;type=entry";
        private const string AtomNamespace = "http://www.w3.org/2005/Atom";
        private XName xnTitle = XName.Get("title", AtomNamespace);
        private XName xnSummary = XName.Get("summary", AtomNamespace);
        private XName xnUpdated = XName.Get("updated", AtomNamespace);
        private XName xnPublished = XName.Get("published", AtomNamespace);

        private XName xnAuthor = XName.Get("author", AtomNamespace);
        private XName xnAuthorName = XName.Get("name", AtomNamespace);

        private XName xnRel = XName.Get("rel", AtomNamespace);
        private XName xnHref = XName.Get("href", AtomNamespace);
        private XName xnLink = XName.Get("link", AtomNamespace);
        private XName xnType = XName.Get("type", AtomNamespace);
        public EntityType currentEntityType;

        public DataServiceQuery CreateQuery(string entitySetName, EntityType entityType)
        {
            currentEntityType = entityType;
            return this.CreateQuery<ODataEntity>(entitySetName);
        }
        public IAsyncResult BeginLoadNavigationProperty(ODataEntity topLevelEntity, string associationName, EntityType associatedType
            , AsyncCallback callback)
        {
            Uri relatedEntityQueryUri = this.CreateRelatedRequestUri(topLevelEntity, associationName, associatedType);
            currentEntityType = associatedType;
            return this.BeginExecute<ODataEntity>(relatedEntityQueryUri,
                callback, null);
        }
        public QueryOperationResponse<TEntity> EndLoadNavigationProperty<TEntity>(IAsyncResult asyncResult)
        {
            return this.EndExecute<TEntity>(asyncResult) as QueryOperationResponse<TEntity>;
        }
        public Uri CreateRelatedRequestUri(ODataEntity topLevelEntity, string associationName, EntityType associatedType)
        {
            if (topLevelEntity.NavigationProperties[associationName] != null)
            {
                return topLevelEntity.NavigationProperties[associationName];
            }
            throw new InvalidOperationException();
        }
        public ODataServiceContext(Uri serviceUri)
            : base(serviceUri)
        {
            this.IgnoreMissingProperties = true;
            this.IgnoreResourceNotFoundException = true;
            this.ReadingEntity += new EventHandler<ReadingWritingEntityEventArgs>(ODataServiceContext_ReadingEntity);
            this.WritingEntity += new EventHandler<ReadingWritingEntityEventArgs>(ODataServiceContext_WritingEntity);
        }

        void ODataServiceContext_WritingEntity(object sender, ReadingWritingEntityEventArgs e)
        {
            ODataEntity entity = e.Entity as ODataEntity;
            if (entity == null)
                return;

            entity.EntityType = currentEntityType;
            entity.OData_Atom = e.Data.ToString().Replace("xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\"", "");
            XElement content = e.Data.Element(XName.Get("content", "http://www.w3.org/2005/Atom"));
            XName xnEntityProperties = XName.Get("properties", ODataMetadataNamespace);
            XElement xePayload = content.Element(xnEntityProperties);
            xePayload.RemoveAll();

            foreach (var property in entity.Columns)
            {
                EntityProperty entityProperty = entity.EntityType.Properties.FirstOrDefault(p => p.PropertyName == property.Key);
                object propertyValue = property.Value;
                if (entityProperty.PropertyType == typeof(DateTime))
                {
                    propertyValue = DateTime.Parse(propertyValue.ToString()).ToString("yyyy-MM-ddThh:mm:ss");
                }
                string EdmTypeName = entityProperty.PropertyType.Name;
                EdmTypeName = EdmTypeName.Replace("System.", "");
                EdmTypeName = "Edm." + EdmTypeName;
                XElement xEntityProperty = new XElement(XName.Get(property.Key, ODataDataNamespace)
                                                                , new XAttribute(XName.Get(ODataTypeName, ODataMetadataNamespace), EdmTypeName)
                                                        , propertyValue);
                xePayload.Add(xEntityProperty);
            }
        }

        private void ProcessFFMappedProperties(EntityType entityType, XElement atom_EntryElement, ref ODataEntity entityInstance)
        {
            foreach (EntityProperty property in entityType.Properties.Where(prop => prop.FeedCustomizationMapping != null))
            {
                switch (property.FeedCustomizationMapping.Mapping)
                {
                    case EPMMappings.Summary:
                        entityInstance[property.PropertyName] = atom_EntryElement.GetValue(xnSummary);
                        break;
                    case EPMMappings.Updated:
                        entityInstance[property.PropertyName] = atom_EntryElement.GetValue(xnUpdated);
                        break;
                    case EPMMappings.Published:
                        entityInstance[property.PropertyName] = atom_EntryElement.GetValue(xnPublished);
                        break;
                    case EPMMappings.Title:
                        entityInstance[property.PropertyName] = atom_EntryElement.GetValue(xnTitle);
                        break;
                    case EPMMappings.AuthorName:
                        entityInstance[property.PropertyName] = atom_EntryElement.Element(xnAuthor).Element(xnAuthorName).Value;
                        break;
                }
            }
        }
        void ODataServiceContext_ReadingEntity(object sender, ReadingWritingEntityEventArgs e)
        {
            ODataEntity entity = e.Entity as ODataEntity;
            if (entity == null)
                return;

            entity.EntityType = currentEntityType;
            entity.OData_Atom = e.Data.ToString().Replace("xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\"", "");
            XElement content = e.Data.Element(XName.Get("content", "http://www.w3.org/2005/Atom"));
            XName xnEntityProperties = XName.Get("properties", ODataMetadataNamespace);
            XElement xePayload = content.Element(xnEntityProperties);
            //if the <content> element does not contain te <m:properties> section , then this is an MLE
            if (xePayload == null)
            {
                entity.MediaUri = content.Attribute(ODataSource).Value;
                entity.MediaType = content.Attribute(ODataTypeName).Value;
                //content.Attribute(ODataTypeName).Value == "image/jpeg" ? content.Attribute(ODataSource).Value : String.Empty;
                xePayload = e.Data.Element(xnEntityProperties);
            }
            if (xePayload == null)
            {
                throw new InvalidOperationException("m:properties element was not found in the atom:entry payload");
            }
            foreach (EntityProperty entityProperty in currentEntityType.Properties)
            {
                //The XName of the property we are going to read from the payload
                XName xnProperty = XName.Get(entityProperty.PropertyName, ODataDataNamespace);
                //Set the value of the property from the payload to the current entity being materialized
                XElement propertyInPayload = xePayload.Element(xnProperty);
                if (propertyInPayload != null)
                {
                    entity[entityProperty.PropertyName] = propertyInPayload.Value;
                }
            }

            #region Find Navigation properties Links
            foreach (XElement linkElement in e.Data.Elements(xnLink))
            {
                XAttribute xaTypeName = linkElement.Attribute(ODataTypeName);
                XAttribute xaRel = linkElement.Attribute(ODatarel);
                XAttribute xaHref = linkElement.Attribute(ODataHref);
                if (xaTypeName != null && (
                    xaTypeName.Value == ODataCollectionNavigationPropertyLinkType ||
                    xaTypeName.Value == ODataReferenceNavigationPropertyLinkType)
                    )
                {
                    entity.NavigationProperties[linkElement.Attribute(ODataTitle).Value] = new Uri(xaHref.Value, UriKind.RelativeOrAbsolute);
                }
            }
            #endregion

            #region Process EPM Mapped Properties
            if (currentEntityType.FeedCustomizationMappings.Count > 0)
            {
                ProcessFFMappedProperties(currentEntityType, e.Data, ref entity);
            }
            #endregion
        }
    }

    public static class XElementExtensions
    {
        public static string GetValue(this XElement element, XName xnElementName)
        {
            if (element.Element(xnElementName) != null)
            {
                return element.Element(xnElementName).Value;
            }
            else
            {
                return string.Empty;
            }
        }
    }
    public interface IODataRequestCreator
    {
        WebRequest CreateGET(Uri requestUri);
    }

    public class AtomRequestCreator : IODataRequestCreator
    {
        #region IODataRequestCreator Members

        public WebRequest CreateGET(Uri requestUri)
        {
            WebRequest.RegisterPrefix(requestUri.OriginalString, WebRequestCreator.ClientHttp);
            HttpWebRequest getRequest = WebRequest.Create(requestUri) as HttpWebRequest;
            getRequest.Accept = "application/atom+xml";
            return getRequest;
        }

        #endregion
    }

    public class JSONRequestCreator : IODataRequestCreator
    {
        #region IODataRequestCreator Members

        public WebRequest CreateGET(Uri requestUri)
        {
            WebRequest.RegisterPrefix(requestUri.OriginalString, WebRequestCreator.ClientHttp);
            HttpWebRequest getRequest = WebRequest.Create(requestUri) as HttpWebRequest;

            getRequest.Accept = "application/json";
            return getRequest;
        }

        #endregion
    }

    public static class WebRequestExtensions
    {
    }
}
